/**
 * @file ServiceFactory.h
 *
 * @author John Wason, PhD
 *
 * @copyright Copyright 2011-2020 Wason Technology, LLC
 *
 * @par License
 * Software License Agreement (Apache License)
 * @par
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * @par
 * http://www.apache.org/licenses/LICENSE-2.0
 * @par
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include "RobotRaconteur/DataTypes.h"
#include "RobotRaconteur/Message.h"
#include "RobotRaconteur/ServiceDefinition.h"

namespace RobotRaconteur
{

class ROBOTRACONTEUR_CORE_API StructureStub;
class ROBOTRACONTEUR_CORE_API ServiceStub;
class ROBOTRACONTEUR_CORE_API ServiceSkel;

class ROBOTRACONTEUR_CORE_API ClientContext;
class ROBOTRACONTEUR_CORE_API ServerContext;

class ROBOTRACONTEUR_CORE_API RobotRaconteurNode;

/**
 * @brief Base class for service factories
 *
 * Service factories are generated by RobotRaconteurGen for static
 * such as languages C++, Java, and C\#,
 * or dynamically generated by dynamic languages such as Python and MATLAB.
 * Service factories must be registered with the node using RobotRaconteurNodeSetup,
 * ClientNodeSetup, ServerNodeSetup, or RobotRaconteurNode::RegisterServiceType().
 *
 * Each service definition corresponds to one service factory. Service factories
 * are not typically accessed by the user once registered.
 *
 */
class ROBOTRACONTEUR_CORE_API ServiceFactory
{

  public:
    RR_SHARED_PTR<RobotRaconteurNode> GetNode();

    void SetNode(const RR_SHARED_PTR<RobotRaconteurNode>& node);

    virtual ~ServiceFactory() {}

    /**
     * @brief Returns the name of the service
     *
     * @return std::string The name of the service
     */
    virtual std::string GetServiceName() = 0;

    /**
     * @brief Return the service definition string used to generate the service factory
     *
     * @return std::string The service definition as a string
     */
    virtual std::string DefString() = 0;

    virtual RR_SHARED_PTR<StructureStub> FindStructureStub(boost::string_ref s) = 0;

    virtual RR_INTRUSIVE_PTR<MessageElementNestedElementList> PackStructure(
        const RR_INTRUSIVE_PTR<RRStructure>& structin) = 0;

    virtual RR_INTRUSIVE_PTR<RRValue> UnpackStructure(
        const RR_INTRUSIVE_PTR<MessageElementNestedElementList>& mstructin) = 0;

    virtual RR_INTRUSIVE_PTR<MessageElementNestedElementList> PackPodArray(
        const RR_INTRUSIVE_PTR<RRPodBaseArray>& structure) = 0;

    virtual RR_INTRUSIVE_PTR<RRPodBaseArray> UnpackPodArray(
        const RR_INTRUSIVE_PTR<MessageElementNestedElementList>& structure) = 0;

    virtual RR_INTRUSIVE_PTR<MessageElementNestedElementList> PackPodMultiDimArray(
        const RR_INTRUSIVE_PTR<RRPodBaseMultiDimArray>& structure) = 0;

    virtual RR_INTRUSIVE_PTR<RRPodBaseMultiDimArray> UnpackPodMultiDimArray(
        const RR_INTRUSIVE_PTR<MessageElementNestedElementList>& structure) = 0;

    virtual RR_INTRUSIVE_PTR<MessageElementNestedElementList> PackNamedArray(
        const RR_INTRUSIVE_PTR<RRNamedBaseArray>& structure) = 0;

    virtual RR_INTRUSIVE_PTR<RRNamedBaseArray> UnpackNamedArray(
        const RR_INTRUSIVE_PTR<MessageElementNestedElementList>& structure) = 0;

    virtual RR_INTRUSIVE_PTR<MessageElementNestedElementList> PackNamedMultiDimArray(
        const RR_INTRUSIVE_PTR<RRNamedBaseMultiDimArray>& structure) = 0;

    virtual RR_INTRUSIVE_PTR<RRNamedBaseMultiDimArray> UnpackNamedMultiDimArray(
        const RR_INTRUSIVE_PTR<MessageElementNestedElementList>& structure) = 0;

    virtual RR_SHARED_PTR<ServiceStub> CreateStub(boost::string_ref objecttype, boost::string_ref path,
                                                  const RR_SHARED_PTR<ClientContext>& context) = 0;

    virtual RR_SHARED_PTR<ServiceSkel> CreateSkel(boost::string_ref objecttype, boost::string_ref path,
                                                  const RR_SHARED_PTR<RRObject>& obj,
                                                  const RR_SHARED_PTR<ServerContext>& context) = 0;

    /**
     * @brief Return the parsed ServiceDefinition
     *
     * @return RR_SHARED_PTR<ServiceDefinition> The service definition after parsing
     */
    virtual RR_SHARED_PTR<ServiceDefinition> ServiceDef();

    virtual std::string RemovePath(boost::string_ref path);

    virtual void DownCastAndThrowException(RobotRaconteurException& exp) = 0;

    virtual RR_SHARED_PTR<RobotRaconteurException> DownCastException(
        const RR_SHARED_PTR<RobotRaconteurException>& exp) = 0;

  private:
    RR_SHARED_PTR<ServiceDefinition> sdef;
    RR_WEAK_PTR<RobotRaconteurNode> node;
};

/**
 * @brief Dynamic service factory base class
 *
 * Dynamic service factories are used to automatically generate service definitions
 * as needed. They are using with dynamic languages such as Python and MATLAB with
 * service definitions pulled by the client.
 *
 */
class ROBOTRACONTEUR_CORE_API DynamicServiceFactory
{
  public:
    virtual ~DynamicServiceFactory() {}

    /**
     * @brief Override to return service factory from a service definition string
     *
     * Factory should return a service factory implementing the provided service definition string.
     *
     * @param def The service definiton as a string
     * @return RR_SHARED_PTR<ServiceFactory> The generated service factory
     */
    virtual RR_SHARED_PTR<ServiceFactory> CreateServiceFactory(boost::string_ref def) = 0;

    /**
     * @brief Override to return collection of service factories from a collection of service definition strings
     *
     * Factory should return a vector service factory implementing the provided vector of service definition string.
     * Clients will provide related service definitions at the same time so they can be verified.
     *
     * @param def The collection of service definiton strings
     * @return RR_SHARED_PTR<ServiceFactory> The generated service factories
     */
    virtual std::vector<RR_SHARED_PTR<ServiceFactory> > CreateServiceFactories(const std::vector<std::string>& def) = 0;
};

#ifndef ROBOTRACONTEUR_NO_CXX11_TEMPLATE_ALIASES
/** @brief Convenience alias for ServiceFactory shared_ptr */
using ServiceFactoryPtr = RR_SHARED_PTR<ServiceFactory>;
/** @brief Convenience alias for DynamicServiceFactory shared_ptr */
using DynamicServiceFactoryPtr = RR_SHARED_PTR<DynamicServiceFactory>;
#endif

} // namespace RobotRaconteur
